#include "../include/conn.h"
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

#define BLOCK_SIZE 4096
// TODO: Implement serialization for send/receive
// TODO: possibly implement protocol for calculating latency and bandwidth
struct sockaddr_in getSockAddr(const char* _addr, port_t port) {
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(_addr);
	return addr;
}

int min(int x, int y) {
	return (x < y)?x:y;
}

Connection::Connection( int _sock,
						const char* _addr, 
						port_t _port, 
						int _proto ) {
//	printf("In constructor 1\n");
	//struct sockaddr_in addr;
	remote_addr.sin_family = AF_INET;
	remote_addr.sin_port = htons(_port);
	remote_addr.sin_addr.s_addr = inet_addr(_addr);
	this->sock = _sock;
	proto = _proto;

	sysErr = 0;
        appErr = 0;
        time(&start_time);
        bytes_transferred = 0;
        bytes_received = 0;
	//Connection(_sock, &addr, proto);
}

Connection::Connection( const char* _addr, 
						port_t _port, 
						int _proto ) {
//	printf("In constructor 2\n");
	this->sock = 0;
	remote_addr.sin_family = AF_INET;
	remote_addr.sin_port = htons(_port);
	remote_addr.sin_addr.s_addr = inet_addr(_addr);
	proto = _proto;

	sysErr = 0;
        appErr = 0;
        time(&start_time);
        bytes_transferred = 0;
        bytes_received = 0;
	//Connection(0, _addr, _port, _proto);
}

Connection::Connection( int _sock,
				struct sockaddr_in* _addr,
				int _proto) {
//	printf("In constructor 3\n");
	this->sock = _sock;
	proto = _proto;
	remote_addr.sin_family = _addr->sin_family;
	remote_addr.sin_port = _addr->sin_port;
	remote_addr.sin_addr.s_addr = _addr->sin_addr.s_addr;
//	printf("Address: %s port: %d\n", inet_ntoa(remote_addr.sin_addr), ntohs(remote_addr.sin_port));
	//TODO: fill in local addr info

	sysErr = 0;
	appErr = 0;
	time(&start_time);
	bytes_transferred = 0;
	bytes_received = 0;
}

const char* Connection::getAddress() {
	return inet_ntoa(remote_addr.sin_addr);
}

addr_t Connection::getByteAddress() {
	return (addr_t)ntohl(remote_addr.sin_addr.s_addr);
}

port_t Connection::getPort() {
	return (port_t)ntohs(remote_addr.sin_port);
}

int Connection::getProto() {
	return proto;
}

int Connection::sendPacket(Packet &_p) {
	// Send size
	uint32_t len = _p.size();
	send(sock, (void*)&len, 4, 0);
	// Send data in blocks
	int i = 0;
	int block = 0;
	int total_bytes = 0;
	while(i < len) {
		block = min((len - i), BLOCK_SIZE);
		int bytes = send(sock, (_p.getData())+i, block, 0);
		if(bytes == block) {
			total_bytes += bytes;
		}
		else {
			sysErr = errno;
			// TODO: set appErr
			return bytes;
		}
		i += block;
	}
	bytes_transferred += total_bytes;
	return total_bytes;
}

Packet Connection::recvPacket() {
	// Receive size
	uint32_t len = 0;
	recv(sock, (void*)&len, 4, 0);
	// Receive packet in blocks
	int i = 0;
	int block = 0;
	int total_bytes = 0;
	uint8_t buf[len];
	while(i < len) {
		block = min((len - i), BLOCK_SIZE);
		int bytes = recv(sock, buf+i, block, 0);
		if(bytes == block) {
			total_bytes += bytes;
		}
		else {
			sysErr = errno;
			// TODO: set appErr
			return Packet(0, 0);
		}
		i += block;
	}
	bytes_received += total_bytes;
	return Packet(buf, total_bytes);
}

int Connection::open() {
	if(sock == 0) {
		if(proto == IPPROTO_UDP) {
			sock = socket(AF_INET, SOCK_DGRAM, proto);
		}
		else {
			sock = socket(AF_INET, SOCK_STREAM, proto);
//			printf("Is TCP: %d\n", sock);
		}
	}
//	printf("Address: %s port: %d\n", inet_ntoa(remote_addr.sin_addr), remote_addr.sin_port);
	int retval = connect(sock,
			(struct sockaddr*)&remote_addr,
			sizeof(struct sockaddr_in) );
	if(retval < 0) {
		sysErr = errno;
		// TODO: set appErr
	}
	return retval;
}

void Connection::closeConn() {
	close(sock);
}

bool Connection::isReady() {
	struct timeval tv;
	fd_set fds;
	tv.tv_sec = 0;
	tv.tv_usec = 0;
	FD_ZERO(&fds);
	FD_SET(sock, &fds);
	return select(sock+1, &fds, NULL, NULL, &tv) > 0;
}

int Connection::getSysErr() {
	return sysErr;
}

int Connection::getAppErr() {
	return appErr;
}

c_id_t Connection::getId() {
	c_id_t id = getByteAddress() << 16;
	id += getPort();
	return id;
}

long Connection::getElapsedTime() {
	return (long)time(NULL)-start_time;
}

counter_t Connection::getBytesTransferred() {
	return bytes_transferred;
}

counter_t Connection::getBytesReceived() {
	return bytes_received;
}
